#include <cstdlib>
#include <cstring>
#include <math.h>
#include <thread>
#include "GraphLib.h"
#include "SDL2/SDL_ttf.h"

#define INT_TO_COLOR(INT) {(Uint8)(((INT)>>4*4)&0xFF),(Uint8)(((INT)>>2*4)&0xFF),(Uint8)(((INT)>>0*8)&0xFF)}
#define PITCH(SURFACE) 4
static inline Uint32& __Pixel__(Surface surface, int x, int y)
{
	return *((Uint32*)(surface->data) + y * surface->w + x);
}

Uint32& Pixel(Surface surface, int x , int y)
{
	static Uint32 t = 0;
	if(x < 0 || x > surface->w - 1 || y < 0 || y > surface->h - 1) return t ;
	return __Pixel__(surface, x, y);
}

/* Surface Method */
Surface CreateSurface(int w, int h)
{
	Surface sur = new __surface_do_not_look_at_me__;
	sur->w = w; sur->h = h;
	sur->data = malloc(w * h * sizeof(Uint32));
	return sur;
}

void FreeSurface(Surface surface)
{
	free(surface->data);
	delete surface;
}

void BlitAsync(Uint32 *sptr, Uint32 *dptr, int w, int h, int src_step, int dst_step)
{
	for (int hv = h; hv > 0; hv--)
	{
		for (int wv = w; wv > 0; wv--)
		{
			Color &sc = *(Color*)sptr, &dc = *(Color*)dptr;
			if (sc.A == 0xFF)
			{
				*dptr = *sptr;
			} else if (sc.A == 0x00) {
			} else {
#define SetCVect(V) dc.V = (sc.A * sc.V + (255 - sc.A) * dc.V) / 255
				SetCVect(R);
				SetCVect(G);
				SetCVect(B);
				dc.A = Max(dc.A, sc.A);
#undef SetCVect
			}
			sptr++; dptr++;
		}
		sptr += src_step;
		dptr += dst_step;
	}
}

void BlitSurface(Surface src, SFC_Rect *srect, Surface dest, SFC_Rect *drect)
{
	SFC_Rect _srect, _drect;
	if (srect != nullptr) { _srect = *(SFC_Rect*)srect; }
	else                  { _srect = {0, 0, src->w, src->h}; }
	if (drect != nullptr) { _drect = *(SFC_Rect*)drect; }
	else                  { _drect = {0, 0, dest->w, dest->h}; }

	SFC_Rect SrcSurRect = {0, 0, src->w, src->h};
	SFC_Rect DestSurRect = {0, 0, dest->w, dest->h};
	_srect = _srect & SrcSurRect;
	_drect = _drect & DestSurRect;

	_srect.w = Min(_srect.w, _drect.w);
	_srect.h = Min(_srect.h, _drect.h);

	if (_srect.w <= 0 || _srect.h <= 0)
		return ;

	unsigned int *sptr = (unsigned int *)src->data + (_srect.x + src->w * _srect.y);
	unsigned int *dptr = (unsigned int *)dest->data + (_drect.x + dest->w * _drect.y);
	int src_step = src->w - _srect.w, dst_step = dest->w - _drect.w;
	int half_h = _srect.h / 2;
	std::thread t1(BlitAsync, sptr,               dptr,                _srect.w, half_h,          src_step, dst_step);
	std::thread t2(BlitAsync, sptr+half_h*src->w, dptr+half_h*dest->w, _srect.w, _srect.h-half_h, src_step, dst_step);
	t1.join();
	t2.join();
}

/* Draw Method */
void DrawLine(Surface surface, int x1, int y1, int x2, int y2, Uint32 color)
{
	int l = sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
	Pixel(surface,x1,y1)=color;
	for(int i = 0; i < l; i++)
	{
		int x=(x1*i+x2*(l-i))/l;
		int y=(y1*i+y2*(l-i))/l;
		Pixel(surface,x,y) = color;
	}
}

void DrawLines(Surface surface, SFC_Point *points, int num, Uint32 color)
{
	for (int i = 1; i < num; i++)
	{
		DrawLine(surface, points[i - 1].x, points[i - 1].y, points[i].x, points[i].y, color);
	}
}

void DrawRect(Surface surface, SFC_Rect *rect, Uint32 color)
{
	DrawLine(surface, rect->x,           rect->y, rect->x,           rect->y + rect->h, color);
	DrawLine(surface, rect->x + rect->w, rect->y, rect->x + rect->w, rect->y + rect->h, color);
	DrawLine(surface, rect->x, rect->y,           rect->x + rect->w, rect->y,           color);
	DrawLine(surface, rect->x, rect->y + rect->h, rect->x + rect->w, rect->y + rect->h, color);
}

void FillRect(Surface surface, const SFC_Rect *rect, Uint32 color)
{
	SFC_Rect _rect;
	if (rect != nullptr) { _rect = *(SFC_Rect*)rect; }
	else                 { _rect = {0, 0, surface->w, surface->h}; }
	_rect.x = Max(_rect.x, 0); _rect.y = Max(_rect.y, 0);
	_rect.w = Min(_rect.w, surface->w - _rect.x);
	_rect.h = Min(_rect.h, surface->h - _rect.y);
	if (_rect.w <= 0 || _rect.h <= 0) return ;
	for (int x = _rect.x; x < _rect.x + _rect.w; x++)
	{
		__Pixel__(surface, x, _rect.y) = color;
	}

	Uint32 *BaseOffset = (Uint32*)surface->data + _rect.x;
	int Length = _rect.w * Min(PITCH(surface), (int)sizeof(Uint32));
	for (int y = _rect.y; y < _rect.y + _rect.h; y++)
		memcpy(BaseOffset + surface->w * y, BaseOffset + surface->w * _rect.y, Length);
	return ;
}

/* Utils Tool */
void SaveRAWHex(const char *file, Surface surface)
{
	int &w = surface->w, &h = surface->h;
	char ft[strlen(file) + 4];
	strcpy(ft, file);
	strcat(ft, ".raw");
	FILE *fp = fopen(ft, "w+");
	fprintf(fp, "Width: %d, Height: %d\n", surface->w, surface->h);
	Uint32 *dp = (Uint32*)surface->data;
	for (int hv = 0; hv < h; hv++)
	{
		for (int wv = 0; wv < w; wv++)
		{
			fprintf(fp, "%08X ", *(dp++));
		}
		fseek(fp, -1, SEEK_CUR);
		fputc('\n', fp);
	}
	fclose(fp);
}

/* Text Method */
int CreateFontHandle(const char *FontFile, int Size)
{
	static bool Inited = false;
	if (Inited == false) {
		TTF_Init();
		Inited = true;
	}
	TTF_Font *font = TTF_OpenFont(FontFile, Size);
	return *(int*)&font;
}

void FreeFontHandle(const int FontHandle)
{
	TTF_CloseFont(*(TTF_Font**)&FontHandle);
}

void TestTextSize(const int FontHandle, const char *Text, int *Width, int *Height)
{
	TTF_SizeText(*(TTF_Font**)&FontHandle, Text, Width, Height);
}

Surface RenderText(const int FontHandle, const char *Text, Uint32 color)
{
	int w, h;
	SDL_Color c;
	c.r = (Uint8)((color&0xFF0000)>>16);
	c.g = (Uint8)((color&0xFF00)>>8);
	c.b = (Uint8)((color&0xFF));
	c.a = 0xFF;
	TTF_Font* font = *(TTF_Font**)&FontHandle;
	SDL_Surface *sdlsur = TTF_RenderText_Blended(font, Text, c);
	w = sdlsur->w;
	h = sdlsur->h;
	Surface sur = CreateSurface(w, h);
	if (SDL_MUSTLOCK(sdlsur)) SDL_LockSurface(sdlsur);
	Uint32 *dptr = (Uint32*)sur->data, *sptr = (Uint32*)sdlsur->pixels;
	for (int i = 0; i < h * sdlsur->pitch / 4; i++, sptr++, dptr++)
	{
		*dptr = *sptr;
	}
	if (SDL_MUSTLOCK(sdlsur)) SDL_UnlockSurface(sdlsur);
	SDL_FreeSurface(sdlsur);
	return sur;
}